#include "autokeybreakerform.h"
#include "ui_autokeybreakerform.h"

#include <QFile>
#include <QFileDialog>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QMessageBox>
#include <QtDebug>

#include "cipher.h"
#include "task/findautokeylenght.h"
#include "task/breakvigenere.h"
#include "loadingform.h"

QByteArray autokey2Vigenere(QByteArray input)
{
    for (int i = 1; i < input.size(); i++)
        input[i] = input[i] ^ input[i-1];
    return input;
}

AutoKeyBreakerForm::AutoKeyBreakerForm(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::AutoKeyBreakerForm),
    hasLoadedFreqs(false)
{
    ui->setupUi(this);
    ui->label_6->hide();
    ui->freqLenght->hide();
}

AutoKeyBreakerForm::~AutoKeyBreakerForm()
{
    delete ui;
}

void AutoKeyBreakerForm::broken(QByteArray key, QByteArray plain)
{
    ui->key->setBinaryData(ui->xorMode->isChecked() ? key : key.left(key.length()/2));
    ui->plainText->setBinaryData(plain);
}

void AutoKeyBreakerForm::failed()
{
    ui->key->clear();
    ui->plainText->setPlainText("cracking failed. are you sure it's autokey?");
}

void AutoKeyBreakerForm::on_load_clicked()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"));
    QFile input(fileName);
    input.open(QIODevice::ReadOnly);
    ui->cipherText->setBinaryData(input.readAll());
}

void AutoKeyBreakerForm::on_loadFreq_clicked()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"));
    if (fileName.isNull()) return;
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName(fileName);

    if (!db.open())
    {
        qDebug() << "impossibile aprire il file";
        return;
    }

    Cipher c;
    if (!ui->binaryMode->isChecked()) c.setAsciiMode();
    alphabet = c.alphabet();
    langFrequencies.clear();
    baseFrequencies.clear();
    for (int i = 0; i < alphabet.length(); i++) baseFrequencies << 0.0;

    for (int i = 1; i <= 7; i++)
    {
        QSqlQuery q = db.exec(QString("SELECT * FROM frequencies WHERE klen == %1")
                              .arg(QString::number(i)));
        if (!q.isActive())
        {
            qDebug() << "il file delle frequenze non è valido (la tabella \"frequencies\" non esiste)";
            return;
        }

        langFrequencies << QHash<QByteArray, qreal>();
    //    for (int i = 0; i < alphabet.size(); i++) langFrequencies << 0.0;

        int keyField = q.record().indexOf("key");
        int freqField = q.record().indexOf("relFreq");
        if (keyField == -1 || freqField == -1)
        {
            qDebug() << "il file delle frequenze non è valido (campi non presenti nella tabella \"frequencies\")";
            return;
        }

        while (q.next())
        {
            langFrequencies[i-1][q.value(keyField).toByteArray()] = q.value(freqField).toReal();
            if (i == 1)
            {
                int j = alphabet.indexOf(q.value(keyField).toByteArray());
                if (j == -1) return;
                baseFrequencies[j] = q.value(freqField).toReal();
            }
        }
    }


    hasLoadedFreqs = true;
    keyFragmentSize = ui->freqLenght->currentIndex()+1;
    ui->langFile->setText(fileName.split("/").last());
}

void AutoKeyBreakerForm::on_save_clicked()
{
    QString fileName = QFileDialog::getSaveFileName(this, tr("Open File"));
    QFile output(fileName);
    output.open(QIODevice::WriteOnly);
    output.write(ui->plainText->binaryData());
}

void AutoKeyBreakerForm::on_decrypt_clicked()
{
    Cipher autokey(alphabet, ui->key->binaryData(), ui->xorMode->isChecked() ? Cipher::XORAutokey : Cipher::Autokey, this);
    autokey.setCipherText(ui->cipherText->binaryData());
    autokey.decrypt();
    ui->plainText->setBinaryData(autokey.plainText());
}

void AutoKeyBreakerForm::on_break_2_clicked()
{
    if (!hasLoadedFreqs) return;
    int kl = ui->keyLenght->text().toInt();
    if (ui->xorMode->isChecked())
    {
        QByteArray input = ui->cipherText->binaryData();
        QByteArray vigeInput;
        vigeInput.resize(input.length());
        for (int i = 0; i < kl; i++)
            vigeInput[i] = input[i];
        for (int i = kl; i < input.size(); i++)
            vigeInput[i] = input[i] ^ vigeInput[i - kl];
        BreakVigenere * delegate = new BreakVigenere(alphabet, baseFrequencies, kl, vigeInput, true);
        connect(delegate, SIGNAL(done(QByteArray,QByteArray)), this, SLOT(broken(QByteArray,QByteArray)));
        connect(delegate, SIGNAL(finished()), delegate, SLOT(deleteLater()));

        LoadingForm * busyLabel = new LoadingForm("Breaking Autokey...", this);
        busyLabel->move((rect().bottomRight() - busyLabel->rect().bottomRight())/2);
        busyLabel->show();
        connect(delegate, SIGNAL(finished()), busyLabel, SLOT(deleteLater()));
        delegate->start();
        return;
    }
    else
    {
        QByteArray input = ui->cipherText->binaryData();
        QByteArray vigeInput;
        vigeInput.resize(input.length());
        for (int i = 0; i < kl; i++)
            vigeInput[i] = input[i];
        for (int i = kl; i < input.size(); i++)
            vigeInput[i] = input[i] - vigeInput[i - kl];
        BreakVigenere * delegate = new BreakVigenere(alphabet, baseFrequencies, 2*kl, vigeInput);
        connect(delegate, SIGNAL(done(QByteArray,QByteArray)), this, SLOT(broken(QByteArray,QByteArray)));
        connect(delegate, SIGNAL(finished()), delegate, SLOT(deleteLater()));

        LoadingForm * busyLabel = new LoadingForm("Breaking Autokey...", this);
        busyLabel->move((rect().bottomRight() - busyLabel->rect().bottomRight())/2);
        busyLabel->show();
        connect(delegate, SIGNAL(finished()), busyLabel, SLOT(deleteLater()));
        delegate->start();
        return;
    }
}


void AutoKeyBreakerForm::on_cEscape_clicked()
{
    ui->plainText->setEscapeMode(BinaryTextEdit::CEscape);
    ui->cipherText->setEscapeMode(BinaryTextEdit::CEscape);
    ui->key->setEscapeMode(BinaryTextEdit::CEscape);
}

void AutoKeyBreakerForm::on_URLEscape_clicked()
{
    ui->plainText->setEscapeMode(BinaryTextEdit::URLEscape);
    ui->cipherText->setEscapeMode(BinaryTextEdit::URLEscape);
    ui->key->setEscapeMode(BinaryTextEdit::URLEscape);
}

void AutoKeyBreakerForm::on_hex_clicked()
{
    ui->plainText->setEscapeMode(BinaryTextEdit::Hex);
    ui->cipherText->setEscapeMode(BinaryTextEdit::Hex);
    ui->key->setEscapeMode(BinaryTextEdit::Hex);
}

void AutoKeyBreakerForm::on_base64_clicked()
{
    ui->plainText->setEscapeMode(BinaryTextEdit::Base64);
    ui->cipherText->setEscapeMode(BinaryTextEdit::Base64);
    ui->key->setEscapeMode(BinaryTextEdit::Base64);
}

void AutoKeyBreakerForm::on_binaryMode_clicked(bool checked)
{
    Cipher c;
    if (checked)
    {
        c.setAsciiMode();
        if (ui->cEscape->isChecked())
            on_cEscape_clicked();
        else if (ui->URLEscape->isChecked())
            on_URLEscape_clicked();
        else if (ui->hex->isChecked())
            on_hex_clicked();
        else // ui->base64->isChecked()
            on_base64_clicked();
    }
    else
    {
        ui->plainText->setEscapeMode(BinaryTextEdit::None);
        ui->cipherText->setEscapeMode(BinaryTextEdit::None);
        ui->key->setEscapeMode(BinaryTextEdit::None);
    }
    alphabet = c.alphabet();
}

void AutoKeyBreakerForm::haveKeyLenght(int keyLenght)
{
    if (keyLenght == -1)
        QMessageBox::information(this, "Fail", "impossibile determinare la lunghezza della chiave.");
    else
        ui->keyLenght->setText(QString::number(keyLenght));
}

void AutoKeyBreakerForm::on_findLength_clicked()
{
    FindAutokeyLenght * fakl = new FindAutokeyLenght(ui->cipherText->binaryData(),
                                                     alphabet,
                                                     ui->maxKeyLenght->text().toInt(),
                                                     ui->xorMode->isChecked(),
                                                     this);
    connect(fakl, SIGNAL(done(int)), this, SLOT(haveKeyLenght(int)));
    connect(fakl, SIGNAL(finished()), fakl, SLOT(deleteLater()));

    LoadingForm * busyLabel = new LoadingForm("Estimating Key Length...", this);
    busyLabel->move((rect().bottomRight() - busyLabel->rect().bottomRight())/2);
    busyLabel->show();
    connect(fakl, SIGNAL(finished()), busyLabel, SLOT(deleteLater()));
    fakl->start();
}
